home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Science⁄Math / VideoToolbox / Demos / TimeVideo.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-08-23  |  27.8 KB  |  668 lines  |  [TEXT/KAHL]

  1. /*
  2. TimeVideo.c
  3. Copyright © Denis G. Pelli, 1992, 1993
  4.  
  5. TimeVideo does a thorough test of the timing and synchronization of each video
  6. screen, as well as the integrity of the clut hardware and software, and saves
  7. the results in a self-explanatory text file, “TimeVideo results”. A discussion
  8. of movies, lookup table animation, and synchronization appears in the
  9. VideoToolbox “Video synch” file. The many video driver bugs uncovered by
  10. TimeVideo and its predecessors are reported in that file. (For example, the
  11. TrueVision NuVista driver assumes zero start in 16- and 32-bit modes.) Please
  12. add your results by emailing your “TimeVideo report” to denis_pelli@isr.syr.edu.
  13.  
  14. Despite Apple’s rules that require it, a few video drivers don’t allow you to
  15. read the clut (i.e. use GDGetEntries), or crash if you attempt it. GDGetEntries
  16. (in GDVideo.c) checks against a list of known offenders and returns a statusErr
  17. is the call is not usable. If we can’t use GDGetEntries then we fall back to a
  18. visual test of the clut, comparing a particular way of loading the clut against
  19. the standard way of loading the clut. Any visible change between these two
  20. conditions suggests an error in the non-standard way of loading the clut.
  21.  
  22. BUGS:
  23. Version 1.0 hung up on a PowerBook 170 with an Envisio video adapter. I didn’t 
  24. have a chance to figure out why. It works fine on my ordinary PowerBook 170.
  25.  
  26. HISTORY:
  27. 8/23/92    dgp    wrote it, based on my TimeCPU.c
  28. 8/26/92    dgp print the driver version only if it's nonzero.
  29.             added summary at end of printout.
  30. 9/9/92    dgp    changed printout of system vbl rate from %.1f to %.2f
  31.             Added movie rate to measurements and printout.
  32. 9/15/92    dgp    made compatible with System 6.04. Added header to results file.
  33. 9/17/92    dgp    Added QUICKLY switch, to select CopyBits or CopyBitsQuickly.
  34. 10/5/92    dgp    cosmetic changes
  35. 10/6/92    dgp    report program version. Use frames/clut update as the criterion for summary.
  36. 10/9/92    dgp    Fixed summary. Too slow means MORE than one frame per clut update.
  37.             Fixed restoration of clut in direct modes (i.e. 16 and 32 bit modes).
  38.             Renamed to GDRestoreDeviceClut and put it in GDVideo.c.
  39. 10/13/92 dgp Tom Busey reported that frames were going uncounted
  40.             during the clut timing, which seems to be a problem with some video drivers.
  41.             TimeVideo now reports a frame count based solely on timing in secs and
  42.             the separately measured frame rate, which seems to be a reliable.
  43.             It also double checks the timing in secs vs frames, and if
  44.             it finds a discrepancy, prints a warning to the screen & file. 
  45. 10/20/92 dgp Added VBL/frame to summary.
  46. 12/8/92    dgp Minor editing of comments.
  47. 12/9/92    dgp    1.01 Added _atexit(RestoreCluts) in case user quits prematurely.
  48. 12/11/92 dgp 1.02 GDRestoreBlackAndWhite allocates stack space for linearTable
  49.             only if we need it.
  50. 12/30/92 dgp 1.03 cosmetic changes.
  51. 12/30/92 dgp 1.04 Enhanced summary to account for NAN when no clut access is allowed.
  52. 12/30/92 dgp 1.05 Use GDClutSize().
  53. 1/6/93    dgp     1.06 Simplified GDRestoreBlackAndWhite, eliminated linearTable.
  54. 1/7/93    dgp  1.07 Included in VideoToolbox-1-93.sea
  55. 1/8/93    dgp     1.08 Added Info-Mac to report file.
  56. 1/11/93    dgp     1.09 In response to bug report from jonathan brecher, added support
  57.             for computers that lack Color QuickDraw.
  58. 1/15/93    dgp    1.10 Added version resource.
  59. 1/18/93    dgp    1.11 Updated the explanatory text.
  60. 1/24/93 dgp    1.12 Updated the explanatory text.
  61. 2/6/93 dgp    1.13 Report ROM version.
  62. 2/15/93 dgp    1.14 Report ROM version as 124+6*256.
  63. 2/22/93    dgp    1.15 Recompiled.
  64. 2/27/93    dgp    1.16 Recompiled with new Identify.c.
  65. 3/10/93    dgp    1.17 Choose a valid mode for which to print the frame rate.
  66.                  Moved all the timing code into GDInfo.c, so this routine just prints.
  67. 3/12/93    dgp        Reorganized the printout and added TestCluts's capabilities.
  68. 3/13/93    dgp        Added high-priority timing.
  69. 3/16/93    dgp    2.0 Added visual hash inspection.
  70. 3/31/93    dgp    2.1 Release version, clut test seems to work.
  71. 4/5/93    dgp    2.2 Recognize gray1 error.
  72. 4/6/93    dgp    2.3 Remove assumption that mode numbers imply certain number of bits.
  73. 4/13/93    dgp    2.3.1 Only set results file type when it is created, not when appending.
  74. 4/15/93    dgp        Display Testing window with gray wedge on test screen.
  75. 4/16/93    dgp    2.4b Restore compatibility with 1-bit qd.
  76. 4/19/93    dgp    2.4.1b Support old Mac II 24-bit NuBus addressing. Fixed two places
  77.                 where I used garbage in place of a linear color table when
  78.                 gdType==directType. Use new GDNewLinearColorTable.
  79. 4/19/93    dgp    2.4.2b Estimate color transformation matrix.
  80. 4/19/93    dgp    2.4.3b Use color transformation matrix to assess gray error.
  81. 4/19/93    dgp    2.4.4b Fixed RectToAddress to always return a 32-bit address,
  82.                     so it won't crash under System 6.
  83. 4/25/93    dgp    2.4.5b Correct for -0.5 bias in estimating rgb gains in TestClut.c
  84. 4/25/93    dgp    2.4.6b Test both color and gray modes; tolerance=3 lsb.
  85. 4/25/93    dgp    2.4.7b Worked around New Palette Manager bugs in original 32-bit QD.
  86. 4/27/93    dgp    2.4.8b Fixed stale-cache bug in SetPixelsQuickly. Cleaned up mode
  87.                     indexing.
  88. 4/28/93    dgp 2.4.9b Rewrote code dealing with SetDepth and HasDepth, wrote GDHasMode().
  89.                     Ignore "isGray" for 16 and 32-bit modes.
  90. 5/11/93    dgp 2.4.9bb Fixed bug in GDTestClutHash in GDTestClut.c in response to bug 
  91.                 report by Jonathan Brecher.
  92. 5/17/93    dgp 2.4.9bbb Changed SetEntriesQuickly to respect the color/gray mode of device.
  93. 5/18/93    dgp 2.4.9b4 Check that card->device is not NULL.
  94. 5/18/93    dgp 2.4.9b5 Suppress printing of identity color transformation.
  95. 5/18/93    dgp 2.4.9b6 Note that 16 and 32 bit depths are always in color mode.
  96. 5/18/93    dgp 2.5 Release version.
  97. 6/2/93    dgp 2.5.1 Updated text reference to info-mac.
  98. 7/7/93    dgp 2.5.2 Recompiled with new GDDacSize, for compatibility with
  99.                 Radius PowerView.
  100. */
  101. #include "VideoToolbox.h"
  102. #include <Packages.h>
  103. #include <Traps.h>
  104. #include <math.h>
  105. #include <assert.h>
  106. #include "GDInfo.h"
  107. void TimeVideo(void);
  108. void PrintVideoInfo(FILE *o[2],VideoInfo *card);
  109. void ReportRGBGains(FILE *o[2],Boolean quickly,Boolean isGray,VideoInfo *card);
  110. long GDColors(GDHandle device);
  111. char setEntriesString[][18]={"GDSetEntries","SetEntriesQuickly"};
  112. char colorGrayString[][6]={"color","gray"};
  113. char ColorGrayString[][6]={"Color","Gray"};
  114. #define VERSION "2.5.2"
  115.  
  116. void main(void)
  117. {
  118.     StackGrow(20000+MAX_SCREENS*sizeof(VideoInfo));
  119.     Require(gestaltOriginalQD);
  120.     TimeVideo();
  121. }
  122.  
  123. void TimeVideo(void)
  124. {
  125.     long system;
  126.     char string[1000],datafilename[]="TimeVideo results";
  127.     unsigned long v[256],colorMax;
  128.     FILE *o[2],*dataFile;
  129.     int newDataFile;
  130.     unsigned long time;
  131.     short *p;
  132.     Str255 todayStr;
  133.     short i,j,d,error,oldPixelSize,oldIsColor,oldMode,mode,cards,modes,width,height;
  134.     short quickly,isGray,doTest,ok;
  135.     double frames,s,vblPerFrame,f;
  136.     Rect r,screenRect;
  137.     WindowPtr window;
  138.     VideoCardClutTest *clut;
  139.     short doHashTest,flags;
  140.     char blankLine[]="\r"    "          " "          " "          " "          "
  141.                             "          " "          " "          " "          " "\r";
  142.     VideoInfo *card;
  143.     
  144.     MaximizeConsoleHeight();
  145.     #if THINK_C
  146.         console_options.title="\pTimeVideo";
  147.     #endif
  148.     printf("\n");    // ask THINK C to initialize quickdraw
  149.     GetDateTime(&time);
  150.     srand(time);
  151.     srandU(time);
  152.     sprintf(string,"Welcome to TimeVideo " VERSION "\n\n"
  153.     "This program will thoroughly test all your video devices and save the "
  154.     "results in the text file “%s”. Don’t be alarmed by the strange "
  155.     "antics of your screens. Everything will soon be back to normal. Just sit back "
  156.     "and enjoy the show.",datafilename);
  157.     sprintf(string,"%s You may quit at any time by hitting Command-period.",string);
  158.     printf(BreakLines(string,80));
  159.     printf("\n\nWould you like the testing to include visual inspections?");
  160.     doHashTest=YesOrNo(1);
  161.     printf("\n");
  162.     if(0)printf(BreakLines("\n"
  163.         "TimeVideo times everything: the video frames, interrupts, and cluts. And it "
  164.         "determines what fraction of the screen you can fill with a real-time movie shown "
  165.         "by CopyBits() or CopyBitsQuickly(). Then it does write-then-read tests of the "
  166.         "clut, to make sure the hardware and software, GDSetEntries or SetEntriesQuickly, "
  167.         "are working correctly.\n",80));
  168.     printf("\n");
  169.     dataFile=fopen(datafilename,"r");
  170.     if(dataFile!=NULL)fclose(dataFile);
  171.     newDataFile=(dataFile==NULL);
  172.     o[0]=stdout;
  173.     o[1]=dataFile=fopen(datafilename,"a");    /* Append to data file */
  174.     if(dataFile==NULL){
  175.         printf("Could neither open nor create “%s”. Perhaps your disk is locked.\n"
  176.             ,datafilename);
  177.         newDataFile=0;
  178.     }
  179.     if(newDataFile){
  180.         SetFileInfo(datafilename,'TEXT','ttxt');
  181.         fprintf(dataFile,BreakLines(
  182.         "This file reports the timing and accuracy of all your video screens, "
  183.         "as measured by TimeVideo, a component of the VideoToolbox. To quickly "
  184.         "test a large number of computers, run TimeVideo from a floppy disk; "
  185.         "all the results will be accumulate in a single results file.\n"
  186.         "\nTHE VIDEO TOOLBOX\n"
  187.         "The VideoToolbox is a "
  188.         "collection of nearly two hundred C subroutines and several demo and utility "
  189.         "programs written to do visual psychophysics with Macintosh computers. It's free "
  190.         "and may not be sold without permission. It should be useful to anyone who wants "
  191.         "to present accurately specified visual stimuli or use the Mac for psychometric "
  192.         "experiments. The text file “Video synch” discusses all the ways of synchronizing "
  193.         "programs to video displays and the many pitfalls to avoid. The TimeVideo "
  194.         "application checks out the timing of all video devices in anticipation of their "
  195.         "use in critical real-time applications, e.g. movies or lookup-table animation. "
  196.         "Low-level routines control video timing and lookup tables, display real-time "
  197.         "movies, and implement the luminance-control algorithms suggested by Pelli and "
  198.         "Zhang. (D.G. Pelli and L. Zhang, 1991, Accurate control of contrast on "
  199.         "microcomputer displays. Vision Research, 31, 1337-1350. Reprints are available.) "
  200.         "High-level routines help analyze psychophysical experiments (e.g. graphing or "
  201.         "maximum-likelihood fitting of psychometric data). This collection has been "
  202.         "continually updated since 1991. Many colleagues have indicated that they are "
  203.         "using the software in their labs. Most of the routines are Mac-specific, but "
  204.         "some very useful routines, e.g. the luminance-control, statistics, and "
  205.         "maximum-likelihood fitting algorithms, could easily be ported to other "
  206.         "computers. To get the latest version of the VideoToolbox, just send me your "
  207.         "mailing address, and I’ll mail you a disk. Or download “VideoToolbox.sea” (a "
  208.         "self-extracting archive) electronically from the Info-Mac or MacPsych archives. "
  209.         "The ftp servers are called sumex-aim.stanford.edu [36.44.0.6] (look in "
  210.         "info-mac/Development/SourceCode/) and ftp.stolaf.edu [130.71.128.9] "
  211.         "(look in pub/macpsych/). "
  212.         "Log in as “anonymous”; any password will do. "
  213.         "It's also on CompuServe in the MacDev forum's Library 4 “C and Pascal” "
  214.         "as VIDEOT.SEA.\n",80));
  215.     fprintf(dataFile,
  216.         "Denis Pelli, Professor of Neuroscience\n"
  217.         "Institute for Sensory Research, Syracuse University, Syracuse, NY 13244-5290, USA\n"
  218.         "denis_pelli@isr.syr.edu\n");
  219.     fprintf(dataFile,BreakLines(
  220.         "\nTIME VIDEO\n"
  221.         "For each video card, TimeVideo measures the video frame rate, frequency of VBL "
  222.         "interrupts (supposed to be one per frame), how long it takes to load the clut, "
  223.         "and how much of the screen you can fill with a real-time one-image-per-frame "
  224.         "movie shown by CopyBits() or CopyBitsQuickly(). It then performs a random "
  225.         "write-then-read test of the Color Lookup Table (clut). This tests the clut "
  226.         "memory hardware and the software used to write and read the clut. We test "
  227.         "writing by GDSetEntries(), which passes the request on to the video driver, and "
  228.         "we also test writing by SetEntriesQuickly(), which accesses the hardware "
  229.         "directly. In either case, the clut is read by GDGetEntries(), which passes the "
  230.         "request on to the video driver. The testing is thorough; many video devices fail "
  231.         "at least part of the test. All the driver errors uncovered to date appear in the "
  232.         "VideoToolbox “Video synch” text file. Add your results by emailing this file to "
  233.         "denis_pelli@isr.syr.edu\n"
  234.         "\nGLOSSARY\n"
  235.         "A video frame is a refresh of your video screen. To show a movie, you will want "
  236.         "to reload the image once per frame; the table shows how big that image can be, "
  237.         "as a fraction of the screen area, and still be reloaded once per frame. "
  238.         "(Some video cards have multiple video “pages” that can be switched by "
  239.         "calling GDSetMode(), though I've never tried it.) A “VBL” "
  240.         "interrupt is produced by your video card driver, nominally once per frame, but "
  241.         "some video drivers produce more, which is poor, but not serious. (The "
  242.         "VBLInstall.c program will deal with it.) Suppressed interrupts during clut "
  243.         "updates are bad. It means that the driver disables the VBL interrupt for too "
  244.         "long while it’s loading the clut. This will throw off any interrupt-based "
  245.         "attempt to count frames. The clut is the color lookup table of your video card. "
  246.         "Lookup table animation, e.g. for temporal modulation of contrast, requires that "
  247.         "you reload the clut once per frame, so it’s very important that this be fast "
  248.         "enough. "
  249.         "The first call to SetEntriesQuickly() for each device is slow--a cache is "
  250.         "filled; the reported times are for subsequent calls. "
  251.         "Each video card can be in “Color” or “Gray” mode, as set by the Control "
  252.         "Panel:Monitors or the Macintosh Toolbox call SetDepth(). "
  253.         "In “Gray” mode all colors are transformed to luminance-equivalent "
  254.         "grays. This is done by the video driver, when loading the clut, "
  255.         "but only if the pixelSize≤8. "
  256.         "TimeVideo measures the driver's color transformation matrix, and reports it "
  257.         "if it is other than the identity transformation:\n"
  258.         "(ROut) (1 0 0) (RIn)\n"
  259.         "(GOut)=(0 1 0)x(GIn)\n"
  260.         "(BOut) (0 0 1) (BIn)\n"
  261.         "The clut tests try loading the clut serially, one entry at a time, and "
  262.         "all at once; some video drivers fail the serial test. For more explanation see "
  263.         "the text file called “Video synch” on the VideoToolbox disk.\n"
  264.         "\n"
  265.         "“ok”= passed all tests.\n"
  266.         "“!gray”= passed the color test, but is supposed to be in gray mode.\n"
  267.         "“!color”= passed the gray test, but is supposed to be in color mode.\n"
  268.         "“!serial”= passed when loaded all at once, but failed when loaded serially.\n"
  269.         "“bad”= read did not equal write and the error is reported explicitly.\n"
  270.         "We know that SetEntriesQuickly is “!serial” on the Quadra, alas.\n"
  271.         "\nThis is a TeachText file, "
  272.         "but you may prefer to open it from THINK C, or a word processor, because the columns "
  273.         "in the tables will only align properly if displayed in a monospaced font.\n\n",80));
  274.     }
  275.     ffprintf(o,"TimeVideo version " VERSION "\n");
  276.     GetDateTime(&time);
  277.     IUDateString(time,longDate,todayStr);
  278.     ffprintf(o,"%#s.\n",todayStr);
  279.  
  280.     // Timer.c requires the Revised Time Manager, which appeared in System 6.0.3.
  281.     // SetDepth() is part of the "new" Palette Manager, which appeared in System 6.0.5.
  282.     Gestalt(gestaltSystemVersion,&system);
  283.     if(system<0x605) {
  284.         PrintfExit("Sorry. Your System is too old; I need at least System 6.0.5.\n");
  285.     }
  286.     card=(VideoInfo *)NewPtr(MAX_SCREENS*sizeof(*card));
  287.     if(card==NULL)PrintfExit("Need more memory. "
  288.         "Use Finder's FIle:Get Info to increase TimeVideo's allocation.\n");
  289.     for(i=0,p=(short *)card;i<GetPtrSize((Ptr)card)/2;i++)*p++=0;    // Zero the cards.
  290.  
  291.     ffprintf(o,"%s\n",BreakLines(IdentifyMachine(),80));
  292.     ffprintf(o,"Tick rate is %.1f Hz.",TickRate());
  293.     ffprintf(o," System-based VBL rate is %.1f Hz.\n",GDFrameRate(NULL));
  294.     if(QD8Exists())_atexit(RestoreCluts);// In case user quits prematurely.
  295.     for(i=0;;i++){
  296.         card[i].basicTested=0;
  297.         card[i].timeTested=0;
  298.         card[i].clutTested=0;
  299.         for(d=0;d<6;d++){
  300.             card[i].depth[d].timeTested=0;
  301.             for(quickly=0;quickly<2;quickly++)for(isGray=0;isGray<2;isGray++){
  302.                 card[i].depth[d].clut[quickly][isGray].read.doTest=0;
  303.                 card[i].depth[d].clut[quickly][isGray].read.tested=0;
  304.                 card[i].depth[d].clut[quickly][isGray].read.errors=0;
  305.                 card[i].depth[d].clut[quickly][isGray].read.errorsAtOnce=0;
  306.                 card[i].depth[d].clut[quickly][isGray].hash.tested=0;
  307.                 card[i].depth[d].clut[quickly][isGray].hash.doTest=0;
  308.                 card[i].depth[d].clut[quickly][isGray].visual.tested=0;
  309.                 card[i].depth[d].clut[quickly][isGray].visual.doTest=0;
  310.             }
  311.         }
  312.         if(QD8Exists()){
  313.             card[i].device=GetScreenDevice(i);
  314.             if(card[i].device==NULL)break;
  315.             oldPixelSize=(**(**card[i].device).gdPMap).pixelSize;
  316.             oldMode=(**card[i].device).gdMode;
  317.             oldIsColor=TestDeviceAttribute(card[i].device,gdDevType);
  318.         }else card[i].device=NULL;
  319.         ffprintf(o,"\n%s\n",BreakLines(IdentifyVideo(card[i].device),80));
  320.         printf("Getting card info. . .\r");
  321.         error=GDInfo(&card[i]);
  322.         
  323.         // Specify what tests we want.
  324.         if(card[i].device!=NULL)isGray=!TestDeviceAttribute(card[i].device,gdDevType);
  325.         else isGray=0;
  326.         for(d=0;d<6;d++)for(quickly=0;quickly<2;quickly++){
  327.             card[i].depth[d].clut[quickly][isGray].read.doTest=1;
  328.             card[i].depth[d].clut[quickly][!isGray].read.doTest=1;
  329.         }
  330.         // Further test deepest directType and clutType modes
  331.         d=5;
  332.         while(card[i].depth[d].pixelSize>=0 && card[i].depth[d].pixelSize<16)d--;
  333.         if(d>=0)for(quickly=0;quickly<2;quickly++){
  334.             card[i].depth[d].clut[quickly][isGray].hash.doTest=doHashTest;
  335.             card[i].depth[d].clut[quickly][!isGray].read.doTest=1;
  336.         }
  337.         d=3;
  338.         while(card[i].depth[d].pixelSize==0 || card[i].depth[d].pixelSize>8)d--;
  339.         if(d>=0)for(quickly=0;quickly<2;quickly++){
  340.             card[i].depth[d].clut[quickly][isGray].hash.doTest=doHashTest;
  341.             card[i].depth[d].clut[quickly][!isGray].read.doTest=1;
  342.         }
  343.  
  344.         // Open window
  345.         if(QD8Exists()){
  346.             r=screenRect=(*card[i].device)->gdRect;
  347.         }else{
  348.             CopyQuickDrawGlobals();    // make sure qd is valid.
  349.             r=screenRect=qd.screenBits.bounds;
  350.         }
  351.         OffsetRect(&r,-r.left,-r.top);
  352.         width=(r.right/=3);
  353.         if(width>256)width=r.right=256;
  354.         height=r.bottom=width;
  355.         CenterRectInRect(&r,&screenRect);
  356.         if(QD8Exists())window=(WindowPtr)NewCWindow(NULL,&r,"\pCLUT Colors",TRUE,noGrowDocProc,(WindowPtr) -1L,0,0);
  357.         else window=NewWindow(NULL,&r,"\pTesting",TRUE,noGrowDocProc,(WindowPtr) -1L,0,0);
  358.             
  359.         for(d=0;d<6;d++)for(isGray=0;isGray<2;isGray++){
  360.             if(card[i].depth[d].pixelSize==0)continue;
  361.             doTest=0;
  362.             for(quickly=0;quickly<2;quickly++){
  363.                 doTest|=card[i].depth[d].clut[quickly][isGray].read.doTest;
  364.                 doTest|=card[i].depth[d].clut[quickly][isGray].hash.doTest;
  365.                 doTest|=card[i].depth[d].clut[quickly][isGray].visual.doTest;
  366.             }
  367.             if(!doTest)continue;
  368.             if(card[i].device!=NULL){
  369.                 if(card[i].depth[d].mode!=(**card[i].device).gdMode
  370.                     || isGray!=!TestDeviceAttribute(card[i].device,gdDevType)){
  371.                     // On Mac IIci, Sys 6.07, HasDepth returns "mode" of 0x100 
  372.                     // at all legal depths.
  373.                     if(card[i].depth[d].pixelSize>0)
  374.                         ok=HasDepth(card[i].device,card[i].depth[d].pixelSize,1,!isGray);
  375.                     else ok=1;
  376.                     // Mac IIci Sys 6.07: SetDepth only accepts mode, not pixelSize.
  377.                     if(ok)error=SetDepth(card[i].device,card[i].depth[d].mode,1,!isGray);
  378.                     if(card[i].depth[d].mode!=(**card[i].device).gdMode
  379.                         || isGray!=!TestDeviceAttribute(card[i].device,gdDevType))continue;
  380.                 }
  381.             }else if(card[i].depth[d].pixelSize!=1 || isGray)continue;
  382.             GDInfo(&card[i]);
  383.             
  384.             // Fill window with spectrum of all colors.
  385.             colorMax=GDColors(card[i].device)-1;
  386.             for(j=0;j<width;j++)v[j]=(j*colorMax+width/2)/(width-1);
  387.             for(j=0;j<height;j++)SetWindowPixelsQuickly(window,0,j,v,width);
  388.             
  389.             if(!card[i].depth[d].timeTested){
  390.                 error=GDInfoTime(&card[i]);
  391.                 for(j=0;j<height;j++)SetWindowPixelsQuickly(window,0,j,v,width);//refresh
  392.             }
  393.             for(quickly=0;quickly<2;quickly++){
  394.                 printf(blankLine);
  395.                 printf("%d-bit %s pixels: testing clut: %s . . .\r"
  396.                     ,card[i].depth[d].pixelSize,colorGrayString[isGray]
  397.                     ,setEntriesString[quickly]);
  398.                 if(quickly)flags=testClutQuicklyFlag;
  399.                 else flags=0;
  400.                 error=GDTestClut(o,flags,&card[i]);
  401.                 error=GDTestClut(o,flags|testClutSeriallyFlag,&card[i]);
  402.                 error=GDTestClutHash(flags,&card[i]);
  403.             }
  404.         }
  405.         DisposeWindow(window);
  406.         if(QD8Exists()){
  407.             // On Mac IIci, Sys 6.07, HasDepth returns "mode" of 0x100 at all legal depths.
  408.             // and SetDepth only accepts mode, not depth.
  409.             ok=HasDepth(card[i].device,oldPixelSize,1,oldIsColor);
  410.             if(ok)error=SetDepth(card[i].device,oldMode,1,oldIsColor);
  411.         }
  412.         printf(blankLine);
  413.         printf("Getting card info. . .\r");
  414.         error=GDInfo(&card[i]);
  415.         printf(blankLine);
  416.         PrintVideoInfo(o,&card[i]);
  417.         if(!QD8Exists())break;
  418.     }
  419.     cards=i;
  420.     if(GDVersion(card[i].device)==100
  421.         && EqualString("\p.Display_Video_Apple_RBV1",GDName(card[i].device),1,1)){
  422.             ffprintf(o,
  423.             "NOTE: the built-in driver in the Mac IIci (.Display_Video_Apple_RBV1 version 0)\n"
  424.             "has a bug that causes it to crash if you attempt to read the clut. A temporary\n"
  425.             "patch has been applied that has fixed the driver until the next reboot, as\n"
  426.             "explained in the VideoToolbox file “Video synch”.\n");
  427.     }
  428.     if(dataFile!=NULL){
  429.         fprintf(dataFile,"\n\n");
  430.         fclose(dataFile);
  431.         sprintf(string,"\nThe text file “%s” explains all the results.\n",datafilename);
  432.         printf(BreakLines(string,80));
  433.     }
  434.     DisposePtr((Ptr)card);
  435. }
  436.  
  437. void PrintVideoInfo(FILE *o[2],VideoInfo *card)
  438. {
  439.     short i,j,k,d,tested,depths,quickly,isGray,grayClut,reportRGB;
  440.     char string[100];
  441.     static char s1[]="%-33s",s2[]="%6s",s3[]="  %-9s\n";
  442.     VideoCardClutTest *clut;
  443.     
  444.     if(card->device!=NULL)isGray=!TestDeviceAttribute(card->device,gdDevType);
  445.     else isGray=0;
  446.     ffprintf(o,"%d-bit dacs. ",card->dacSize);
  447.     ffprintf(o,"%dx%d pixels. ",card->width,card->height);
  448. //    ffprintf(o,"%s mode. ",ColorGrayString[isGray]);
  449.     ffprintf(o,"\n");
  450.  
  451.     if(card->basicTested){
  452.         ffprintf(o,s1,"pixel size");
  453.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  454.             sprintf(string,"%d   ",card->depth[d].pixelSize);
  455.             ffprintf(o,s2,string);
  456.         }
  457.         ffprintf(o,s3,"bits");
  458.     
  459.         if(QD8Exists()){
  460.             // I haven't tried to look for 1-bit QD's page swapping scheme
  461.             ffprintf(o,s1,"pages");
  462.             for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  463.                 sprintf(string,"%d   ",card->depth[d].pages);
  464.                 ffprintf(o,s2,string);
  465.             }
  466.             ffprintf(o,s3,"");
  467.     
  468.             // Meaningless in 1-bit QD
  469.             ffprintf(o,s1,"mode");
  470.             for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  471.                 sprintf(string,"0x%x ",card->depth[d].mode);
  472.                 ffprintf(o,s2,string);
  473.             }
  474.             ffprintf(o,s3,"");
  475.         }
  476.     }
  477.  
  478.     if(card->timeTested){
  479.         ffprintf(o,s1,"frame rate");
  480.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  481.             sprintf(string,"%.1f ",card->depth[d].frameRate);
  482.             ffprintf(o,s2,string);
  483.         }
  484.         ffprintf(o,s3,"Hz");
  485.     
  486.         ffprintf(o,s1,"interrupts per frame");
  487.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  488.             sprintf(string,"%.1f ",card->depth[d].vblPerFrame);
  489.             ffprintf(o,s2,string);
  490.         }
  491.         ffprintf(o,s3,"");
  492.     
  493.         ffprintf(o,s1,"CopyBits movie size");
  494.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  495.             sprintf(string,"%.2f",card->depth[d].movieRate/card->depth[d].frameRate);
  496.             ffprintf(o,s2,string);
  497.         }
  498.         ffprintf(o,s3,"screen");
  499.     
  500.         ffprintf(o,s1,"CopyBitsQuickly movie size");
  501.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  502.             sprintf(string,"%.2f",card->depth[d].movieRateQuickly/card->depth[d].frameRate);
  503.             ffprintf(o,s2,string);
  504.         }
  505.         ffprintf(o,s3,"screen");
  506.     
  507.         ffprintf(o,s1,"CopyBits data rate");
  508.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  509.             sprintf(string,"%.2f",card->depth[d].movieRate
  510.                 *card->depth[d].pixelSize
  511.                 *card->width*card->height/8./1024./1024.);
  512.             ffprintf(o,s2,string);
  513.         }
  514.         ffprintf(o,s3,"MB/s");
  515.     
  516.         ffprintf(o,s1,"CopyBitsQuickly data rate");
  517.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  518.             sprintf(string,"%.2f",card->depth[d].movieRateQuickly
  519.                 *card->depth[d].pixelSize
  520.                 *card->width*card->height/8./1024./1024.);
  521.             ffprintf(o,s2,string);
  522.         }
  523.         ffprintf(o,s3,"MB/s");
  524.     }
  525.     
  526.     if(card->device!=NULL && (**card->device).gdType!=fixedType && card->timeTested){
  527.         ffprintf(o,s1,"GDSetEntries duration");
  528.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  529.             sprintf(string,"%.2f",card->depth[d].framesPerClutUpdate);
  530.             ffprintf(o,s2,string);
  531.         }
  532.         ffprintf(o,s3,"frames");
  533.  
  534.         ffprintf(o,s1,"GDSetEntries suppresses ints. for");
  535.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  536.             sprintf(string,"%.1f ",card->depth[d].missingFramesPerClutUpdate);
  537.             ffprintf(o,s2,string);
  538.         }
  539.         ffprintf(o,s3,"frames");
  540.  
  541.         ffprintf(o,s1,"GDSetEntriesHighPriority duration");
  542.         for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  543.             sprintf(string,"%.2f",card->depth[d].framesPerClutUpdateHighPriority);
  544.             ffprintf(o,s2,string);
  545.         }
  546.         ffprintf(o,s3,"frames");
  547.  
  548.         if(card->setEntriesQuickly){
  549.             ffprintf(o,s1,"SetEntriesQuickly duration");
  550.             for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  551.                 sprintf(string,"%.2f",card->depth[d].framesPerClutUpdateQuickly);
  552.                 ffprintf(o,s2,string);
  553.             }
  554.             ffprintf(o,s3,"frames");
  555.         }
  556.     }
  557.     if(card->device!=NULL && (**card->device).gdType!=fixedType && card->clutTested){
  558.         for(quickly=0;quickly<2;quickly++)for(isGray=0;isGray<2;isGray++){
  559.             tested=0;
  560.             for(d=0;d<6;d++)if(card->depth[d].pixelSize)
  561.                 if(card->depth[d].clut[quickly][isGray].hash.tested)tested=1;
  562.             if(tested){
  563.                 sprintf(string,"%s hash inspection",setEntriesString[quickly]);
  564.                 ffprintf(o,s1,string);
  565.                 for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  566.                     clut=&card->depth[d].clut[quickly][isGray];
  567.                     if(clut->hash.tested){
  568.                         if(clut->hash.errors)
  569.                             sprintf(string,"fail");
  570.                         else sprintf(string,"ok");
  571.                     }else sprintf(string,"");
  572.                     ffprintf(o,s2,string);
  573.                 }
  574.                 ffprintf(o,s3,"");
  575.             }
  576.         }
  577.     
  578.         for(quickly=0;quickly<2;quickly++)for(isGray=0;isGray<2;isGray++){
  579.             tested=0;
  580.             for(d=0;d<6;d++)if(card->depth[d].pixelSize)
  581.                 if(card->depth[d].clut[quickly][isGray].read.tested)tested=1;
  582.             if(tested){
  583.                 reportRGB=0;
  584.                 sprintf(string,"%s: %s test"
  585.                     ,colorGrayString[isGray],setEntriesString[quickly]);
  586.                 ffprintf(o,s1,string);
  587.                 for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  588.                     clut=&card->depth[d].clut[quickly][isGray];
  589.                     grayClut=isGray && card->depth[d].pixelSize<=8;
  590.                     if(clut->read.tested){
  591.                         if(clut->read.errors){
  592.                             if(clut->read.errorsAtOnce)sprintf(string,"bad");
  593.                             else sprintf(string,"!serial");
  594.                         }else if(grayClut!=clut->read.identity)sprintf(string,"ok");
  595.                         else sprintf(string,"!%s",colorGrayString[grayClut]);
  596.                     }else sprintf(string,"");
  597.                     ffprintf(o,s2,string);
  598.                     if(card->depth[d].pixelSize<=8)reportRGB=!clut->read.identity;
  599.                 }
  600.                 ffprintf(o,s3,"");
  601.                 if(reportRGB)ReportRGBGains(o,quickly,isGray,card);
  602.             }
  603.     
  604.             tested=0;
  605.             for(d=0;d<6;d++)if(card->depth[d].pixelSize)
  606.                 if(card->depth[d].clut[quickly][isGray].visual.tested)tested=1;
  607.             if(tested){
  608.                 sprintf(string,"%s visual inspection",setEntriesString[quickly]);
  609.                 ffprintf(o,s1,string);
  610.                 for(d=0;d<6;d++)if(card->depth[d].pixelSize){
  611.                     clut=&card->depth[d].clut[quickly][isGray];
  612.                     if(clut->visual.tested){
  613.                         if(clut->visual.errors){
  614.                             if(!clut->visual.errorsAtOnce)sprintf(string,"!serial");
  615.                             else sprintf(string,"bad");
  616.                         }else sprintf(string,"ok");
  617.                     }else sprintf(string,"");
  618.                     ffprintf(o,s2,string);
  619.                 }
  620.                 ffprintf(o,s3,"");
  621.             }
  622.         }
  623.     }
  624.     for(i=0;i<2;i++)if(o[i]!=NULL)fflush(o[i]);    // Save to disk, just in case.
  625. }
  626.  
  627. void ReportRGBGains(FILE *o[2],Boolean quickly,Boolean isGray,VideoInfo *card)
  628. {
  629.     short j,k,d,depths;
  630.     VideoCardClutTest *clut;
  631.     double rgbGain[3][3],rgbError[3];
  632.     Boolean integralGains,bad;
  633.  
  634.     depths=0;
  635.     for(j=0;j<3;j++){
  636.         for(k=0;k<3;k++)rgbGain[j][k]=0.;
  637.         rgbError[j]=0.0;
  638.     }
  639.     integralGains=1;
  640.     for(d=0;d<6;d++)if(card->depth[d].pixelSize && card->depth[d].pixelSize<=8){
  641.         clut=&card->depth[d].clut[quickly][isGray];
  642.         bad=0;
  643.         for(j=0;j<3;j++)bad|=clut->read.rgbError[j]>2.0*(1<<(16-4));
  644.         if(bad)continue;
  645.         for(j=0;j<3;j++){
  646.             for(k=0;k<3;k++)rgbGain[j][k]+=clut->read.rgbGain[j][k];
  647.             rgbError[j]+=clut->read.rgbError[j];
  648.         }
  649.         depths++;
  650.     }
  651.     for(j=0;j<3;j++){
  652.         for(k=0;k<3;k++){
  653.             rgbGain[j][k]/=depths;
  654.             integralGains&=(rgbGain[j][k]==floor(rgbGain[j][k]));
  655.         }
  656.         rgbError[j]/=depths;
  657.     }
  658.     if(IsFinite(rgbGain[0][0]))for(j=0;j<3;j++){
  659.         ffprintf(o," (%cOut±%4.1f%%)%c"
  660.             ,"RGB"[j],rgbError[j]*100./USHRT_MAX," = "[j]);
  661.         if(integralGains)ffprintf(o,"(%1.0f %1.0f %1.0f)"
  662.             ,rgbGain[j][0],rgbGain[j][1],rgbGain[j][2]);
  663.         else ffprintf(o,"(%3.2f %3.2f %3.2f)"
  664.             ,rgbGain[j][0],rgbGain[j][1],rgbGain[j][2]);
  665.         ffprintf(o,"%c(%cIn)\n"," x "[j],"RGB"[j]);
  666.     }
  667. }
  668.